Core: trivially_relocatable

This commit is contained in:
Mirek Fidler 2024-08-23 08:35:19 +02:00
parent abb5c9ae17
commit 3638778b2e
20 changed files with 140 additions and 136 deletions

View file

@ -12,8 +12,8 @@ protected:
void ReAlloc(int newalloc); void ReAlloc(int newalloc);
void Add0(); void Add0();
void DeepCopy0(const BiVector& src); void DeepCopy0(const BiVector& src);
T *AddHead0() { AssertMoveable<T>(); Add0(); return &vector[start = Ix(alloc - 1)/*(start + alloc - 1) % alloc*/]; } T *AddHead0() { Add0(); return &vector[start = Ix(alloc - 1)/*(start + alloc - 1) % alloc*/]; }
T *AddTail0() { AssertMoveable<T>(); Add0(); return &vector[EI()]; } T *AddTail0() { Add0(); return &vector[EI()]; }
void Zero() { start = items = alloc = 0; vector = NULL; } void Zero() { start = items = alloc = 0; vector = NULL; }
void Free(); void Free();
void Pick(BiVector&& x) { vector = pick(x.vector); start = x.start; items = x.items; void Pick(BiVector&& x) { vector = pick(x.vector); start = x.start; items = x.items;
@ -21,6 +21,8 @@ protected:
void Copy(T *dst, int start, int count) const; void Copy(T *dst, int start, int count) const;
public: public:
static_assert(is_trivially_relocatable<T> || is_upp_guest<T>);
int GetCount() const { return items; } int GetCount() const { return items; }
bool IsEmpty() const { return items == 0; } bool IsEmpty() const { return items == 0; }
void Clear(); void Clear();

View file

@ -54,4 +54,6 @@ inline bool IsInf(const Complex& x) { return IsInf(x.real()) || IsInf(x.i
inline bool IsFin(const Complex& x) { return IsFin(x.real()) && IsFin(x.imag()); } inline bool IsFin(const Complex& x) { return IsFin(x.real()) && IsFin(x.imag()); }
VALUE_COMPARE(Complex) VALUE_COMPARE(Complex)
NTL_MOVEABLE(Complex)
template <> inline constexpr bool is_trivially_relocatable<Complex> = true;

View file

@ -1,7 +1,7 @@
#ifndef CORE_H #ifndef CORE_H
#define CORE_H #define CORE_H
#define UPP_VERSION 0x20220300 #define UPP_VERSION 0x20240900
#ifndef flagMT #ifndef flagMT
#define flagMT // MT is now always on #define flagMT // MT is now always on
@ -410,12 +410,6 @@ String AsString(const i16x8& x);
String AsString(const i8x16& x); String AsString(const i8x16& x);
#endif #endif
#ifdef PLATFORM_WIN32
NTL_MOVEABLE(POINT)
NTL_MOVEABLE(SIZE)
NTL_MOVEABLE(RECT)
#endif
} }
#if (defined(TESTLEAKS) || defined(HEAPDBG)) && defined(COMPILER_GCC) && defined(UPP_HEAP) #if (defined(TESTLEAKS) || defined(HEAPDBG)) && defined(COMPILER_GCC) && defined(UPP_HEAP)

View file

@ -65,7 +65,7 @@ Value ParseJSON(const char *s)
String AsJSON(Time tm) String AsJSON(Time tm)
{ {
return IsNull(tm) ? "null" : "\"\\/Date(" + AsString(1000 * (tm - Time(1970, 1, 1))) + ")\\/\""; return IsNull(tm) ? String("null") : "\"\\/Date(" + AsString(1000 * (tm - Time(1970, 1, 1))) + ")\\/\"";
} }
String AsJSON(Date dt) String AsJSON(Date dt)

View file

@ -444,7 +444,7 @@ String LanguageInfo::FormatDouble(double value, int digits, int FD_flags, int fi
return Null; return Null;
return NlsFormatRaw(UPP::FormatDouble(value, digits, FD_flags), return NlsFormatRaw(UPP::FormatDouble(value, digits, FD_flags),
FD_flags & FD_NOTHSEPS ? String() : thousand_separator, FD_flags & FD_NOTHSEPS ? String() : thousand_separator,
FD_flags & FD_COMMA ? "," : decimal_point); FD_flags & FD_COMMA ? String(",") : decimal_point);
} }
String LanguageInfo::FormatDate(Date date) const String LanguageInfo::FormatDate(Date date) const

View file

@ -233,13 +233,13 @@ String FileExtToMIME(const String& ext)
if(*h == '.') if(*h == '.')
h = h.Mid(1); h = h.Mid(1);
int q = sEXT().Find(h); int q = sEXT().Find(h);
return q >= 0 ? sMIME()[q] : "application/octet-stream"; return q >= 0 ? sMIME()[q] : String("application/octet-stream");
} }
String MIMEToFileExt(const String& mime) String MIMEToFileExt(const String& mime)
{ {
int q = sMIME().Find(ToLower(mime)); int q = sMIME().Find(ToLower(mime));
return q >= 0 ? sEXT()[q] : "bin"; return q >= 0 ? sEXT()[q] : String("bin");
} }
} }

View file

@ -330,14 +330,12 @@ void memcpy128(void *p, const void *q, size_t count)
template <class T> template <class T>
void memcpy_t(void *t, const T *s, size_t count) void memcpy_t(void *t, const T *s, size_t count)
{ {
#ifdef CPU_X86
if((sizeof(T) & 15) == 0) if((sizeof(T) & 15) == 0)
memcpy128(t, s, count * (sizeof(T) >> 4)); memcpy128(t, s, count * (sizeof(T) >> 4));
else else
if((sizeof(T) & 7) == 0) if((sizeof(T) & 7) == 0)
memcpy64(t, s, count * (sizeof(T) >> 3)); memcpy64(t, s, count * (sizeof(T) >> 3));
else else
#endif
if((sizeof(T) & 3) == 0) if((sizeof(T) & 3) == 0)
memcpy32(t, s, count * (sizeof(T) >> 2)); memcpy32(t, s, count * (sizeof(T) >> 2));
else else

View file

@ -343,7 +343,7 @@ public:
~String0() { Free(); } ~String0() { Free(); }
}; };
class String : public Moveable<String, AString<String0> > { class String : Moveable<String>, public AString<String0> {
void Swap(String& b) { String0::Swap(b); } void Swap(String& b) { String0::Swap(b); }
#ifdef _DEBUG #ifdef _DEBUG
@ -799,7 +799,7 @@ public:
// WString0& operator=(const WString0& s) { Free(); Set0(s); return *this; } // WString0& operator=(const WString0& s) { Free(); Set0(s); return *this; }
}; };
class WString : public Moveable<WString, AString<WString0> > class WString : Moveable<WString>, public AString<WString0>
{ {
void Swap(WString& b) { WString0::Swap(b); } void Swap(WString& b) { WString0::Swap(b); }

View file

@ -130,74 +130,108 @@ inline void Fill(unsigned char *t, const unsigned char *lim, const unsigned char
inline void Copy(unsigned char *dst, const unsigned char *src, const unsigned char *lim) inline void Copy(unsigned char *dst, const unsigned char *src, const unsigned char *lim)
{ memcpy8(dst, src, size_t((byte *)lim - (byte *)src)); } { memcpy8(dst, src, size_t((byte *)lim - (byte *)src)); }
#ifdef NO_MOVEABLE_CHECK template <class T>
inline void DeepCopyConstructFill(T *t, const T *end, const T& x) {
while(t != end)
new(t++) T(clone(x));
}
template <class T> template <class T>
inline void AssertMoveable(T *) {} inline void Construct(T *t, const T *lim) {
while(t < lim)
new(t++) T;
}
#define MoveableTemplate(T) template <class T>
inline void Destruct(T *t)
template <class T, class B = EmptyClass>
class Moveable : public B
{ {
}; t->~T();
}
template <class T> template <class T>
struct Moveable_ { inline void Destroy(T *t, const T *end)
}; {
while(t != end)
#define NTL_MOVEABLE(T) Destruct(t++);
}
#else
template <class T> template <class T>
inline void AssertMoveablePtr(T, T) {} struct TriviallyRelocatable {};
template <class T>
inline void AssertMoveable0(T *t) { AssertMoveablePtr(&**t, *t); }
// COMPILATION ERROR HERE MEANS TYPE T WAS NOT MARKED AS Moveable
template <class T, class B = EmptyClass> template <class T, class B = EmptyClass>
struct Moveable : public B { struct Moveable : TriviallyRelocatable<T> {};
friend void AssertMoveable0(T *) {}
}; template <class T> // backward compatiblity
struct Moveable_ : Moveable<T> {};
template <class T> template <class T>
struct Moveable_ { inline constexpr bool is_trivially_relocatable = std::is_trivially_copyable_v<T> ||
friend void AssertMoveable0(T *) {} std::is_base_of_v<TriviallyRelocatable<T>, T>;
};
template <class T> template <class T>
inline void AssertMoveable(T *t = 0) { if(t) AssertMoveable0(t); } inline constexpr bool is_upp_guest = false;
#if defined(COMPILER_MSC) || defined(COMPILER_GCC) && (__GNUC__ < 4 || __GNUC_MINOR__ < 1) template <class T>
#define NTL_MOVEABLE(T) inline void AssertMoveable0(T *) {} inline typename std::enable_if_t<is_trivially_relocatable<T>> Move(T *dst, T *src)
#else {
#define NTL_MOVEABLE(T) template<> inline void AssertMoveable<T>(T *) {} memcpy(dst, src, sizeof(T));
#endif }
#endif template <class T>
inline typename std::enable_if_t<!is_trivially_relocatable<T>> Move(T *dst, T *src)
{
new(dst) T(pick(*src));
Destruct(src);
}
NTL_MOVEABLE(bool) template <class T>
NTL_MOVEABLE(char) inline void InMove(T *dst, T *src, int n)
NTL_MOVEABLE(signed char) {
NTL_MOVEABLE(unsigned char) if(is_trivially_relocatable<T>)
NTL_MOVEABLE(short) memmove(dst, src, n * sizeof(T));
NTL_MOVEABLE(unsigned short) else {
NTL_MOVEABLE(int) if(n <= 0)
NTL_MOVEABLE(unsigned int) return;
NTL_MOVEABLE(long) dst += n - 1;
NTL_MOVEABLE(unsigned long) T *s = src + n - 1;
NTL_MOVEABLE(int64) for(;;) {
NTL_MOVEABLE(uint64) Move(dst, s);
NTL_MOVEABLE(float) if(s == src) break;
NTL_MOVEABLE(double) dst--;
NTL_MOVEABLE(void *) s--;
NTL_MOVEABLE(const void *) }
}
}
#if defined(_NATIVE_WCHAR_T_DEFINED) || defined(COMPILER_GCC) template <class T>
NTL_MOVEABLE(wchar_t) inline void ReMove(T *dst, T *src, int n)
#endif {
if(is_trivially_relocatable<T>)
memmove(dst, src, n * sizeof(T));
else {
T *lim = src + n;
while(src != lim)
Move(dst++, src++);
}
}
template <class T>
inline void Relocate(T *dst, T *src, int n)
{
if(is_trivially_relocatable<T>)
memcpy_t(dst, src, n);
else {
T *lim = src + n;
while(src != lim)
Move(dst++, src++);
}
}
template <class T, class S>
inline void DeepCopyConstruct(T *t, const S *s, const S *end) {
while(s != end)
new (t++) T(clone(*s++));
}
template <class T, class B = EmptyClass> template <class T, class B = EmptyClass>
class WithClone : public B { class WithClone : public B {
@ -216,8 +250,7 @@ public:
}; };
template <class T, class B = EmptyClass> template <class T, class B = EmptyClass>
class MoveableAndDeepCopyOption : public B { class MoveableAndDeepCopyOption : public Moveable<T> {
friend void AssertMoveable0(T *) {}
#ifdef DEPRECATED #ifdef DEPRECATED
friend T& operator<<=(T& dest, const T& src) friend T& operator<<=(T& dest, const T& src)
{ if(&dest != &src) { (&dest)->~T(); ::new(&dest) T(src, 1); } return dest; } { if(&dest != &src) { (&dest)->~T(); ::new(&dest) T(src, 1); } return dest; }
@ -421,9 +454,6 @@ public:
STL_ITERATOR_COMPATIBILITY STL_ITERATOR_COMPATIBILITY
}; };
unsigned Pow2Bound(unsigned i);
unsigned PrimeBound(unsigned i);
hash_t memhash(const void *ptr, size_t size); hash_t memhash(const void *ptr, size_t size);
template <class T> template <class T>
@ -480,12 +510,6 @@ public:
template <class T> CombineHash& operator<<(const T& x) { Do(x); return *this; } template <class T> CombineHash& operator<<(const T& x) { Do(x); return *this; }
}; };
template <int size>
struct Data_S_ : Moveable< Data_S_<size> >
{
byte filler[size];
};
template <class C> template <class C>
bool IsEqualMap(const C& a, const C& b) bool IsEqualMap(const C& a, const C& b)
{ {

View file

@ -155,6 +155,21 @@ public:
Tuple(const Args... args) : Base(args...) {}; Tuple(const Args... args) : Base(args...) {};
}; };
template <typename A, typename B>
inline constexpr bool is_trivially_relocatable<Tuple<A, B>> = is_trivially_relocatable<A> &&
is_trivially_relocatable<B>;
template <typename A, typename B, typename C>
inline constexpr bool is_trivially_relocatable<Tuple<A, B, C>> = is_trivially_relocatable<A> &&
is_trivially_relocatable<B> &&
is_trivially_relocatable<C>;
template <typename A, typename B, typename C, typename D>
inline constexpr bool is_trivially_relocatable<Tuple<A, B, C, D>> = is_trivially_relocatable<A> &&
is_trivially_relocatable<B> &&
is_trivially_relocatable<C> &&
is_trivially_relocatable<D>;
template <typename... Args> template <typename... Args>
Tuple<Args...> MakeTuple(const Args... args) { Tuple<Args...> MakeTuple(const Args... args) {
return Tuple<Args...>(args...); return Tuple<Args...>(args...);

View file

@ -77,7 +77,7 @@ class AssignValueTypeNo : public ValueType<T, type, B> {};
template <class T> template <class T>
dword GetValueTypeNo() { return ValueTypeNo((T*)NULL); } dword GetValueTypeNo() { return ValueTypeNo((T*)NULL); }
class Value : Moveable_<Value> { class Value : Moveable<Value> {
public: public:
class Void { class Void {
protected: protected:
@ -292,7 +292,7 @@ public:
friend void Swap(Value& a, Value& b) { Swap(a.data, b.data); } friend void Swap(Value& a, Value& b) { Swap(a.data, b.data); }
typedef ConstIteratorOf<Vector<Value>> const_iterator; typedef const Value *const_iterator;
const_iterator begin() const { return GetVA().begin(); } const_iterator begin() const { return GetVA().begin(); }
const_iterator end() const { return GetVA().end(); } const_iterator end() const { return GetVA().end(); }

View file

@ -9,33 +9,6 @@ void BREAK_WHEN_PICKED(T& x)
} }
#endif #endif
template <class T>
inline void DeepCopyConstructFill(T *t, const T *end, const T& x) {
while(t != end)
new(t++) T(clone(x));
}
template <class T>
inline void Construct(T *t, const T *lim) {
while(t < lim)
new(t++) T;
}
template <class T>
inline void Destroy(T *t, const T *end)
{
while(t != end) {
t->~T();
t++;
}
}
template <class T, class S>
inline void DeepCopyConstruct(T *t, const S *s, const S *end) {
while(s != end)
new (t++) T(clone(*s++));
}
template <class T> template <class T>
class Buffer : Moveable< Buffer<T> > { class Buffer : Moveable< Buffer<T> > {
T *ptr; T *ptr;
@ -165,6 +138,8 @@ template <class U> class Index;
template <class T> template <class T>
class Vector : public MoveableAndDeepCopyOption< Vector<T> > { class Vector : public MoveableAndDeepCopyOption< Vector<T> > {
static_assert(is_trivially_relocatable<T> || is_upp_guest<T>);
T *vector; T *vector;
int items; int items;
int alloc; int alloc;
@ -283,9 +258,8 @@ public:
~Vector() { ~Vector() {
Free(); Free();
return; // Constraints: return; // Constraints:
AssertMoveable((T *)0); // T must be moveable
} }
// Pick assignment & copy. Picked source can only do Clear(), ~Vector(), operator=, operator <<= // Pick assignment & copy. Picked source can only do Clear(), ~Vector(), operator=, operator <<=
Vector(Vector&& v) { Pick(pick(v)); } Vector(Vector&& v) { Pick(pick(v)); }
void operator=(Vector&& v) { if(this != &v) { Free(); Pick(pick(v)); } } void operator=(Vector&& v) { if(this != &v) { Free(); Pick(pick(v)); } }

View file

@ -122,7 +122,7 @@ bool Vector<T>::ReAlloc(int newalloc)
alloc = newalloc == INT_MAX ? INT_MAX // maximum alloc reached alloc = newalloc == INT_MAX ? INT_MAX // maximum alloc reached
: (int)((sz - sz0) / sizeof(T) + newalloc); // adjust alloc to real memory size : (int)((sz - sz0) / sizeof(T) + newalloc); // adjust alloc to real memory size
if(vector && newvector) if(vector && newvector)
memcpy_t((T *)newvector, vector, items); Relocate((T *)newvector, vector, items);
vector = (T *)newvector; vector = (T *)newvector;
return alloced; return alloced;
} }
@ -306,7 +306,7 @@ void Vector<T>::Remove(int q, int count) {
ASSERT(q >= 0 && q <= items - count && count >= 0); ASSERT(q >= 0 && q <= items - count && count >= 0);
if(count == 0) return; if(count == 0) return;
Destroy(vector + q, vector + q + count); Destroy(vector + q, vector + q + count);
memmove((void *)(vector + q), (void *)(vector + q + count), (items - q - count) * sizeof(T)); ReMove(vector + q, vector + q + count, items - q - count);
items -= count; items -= count;
} }
@ -319,18 +319,17 @@ void Vector<T>::Remove(const int *sorted_list, int n)
for(;;) { for(;;) {
ASSERT(pos < items); ASSERT(pos < items);
if(pos == *sorted_list) { if(pos == *sorted_list) {
(vector + pos)->~T(); Destruct(vector + pos);
pos++; pos++;
sorted_list++; sorted_list++;
if(--n == 0) break; if(--n == 0) break;
ASSERT(*sorted_list >= pos); ASSERT(*sorted_list >= pos);
} }
else else
*((Data_S_<sizeof(T)>*)vector + npos++) Move(vector + npos++, vector + pos++);
= *((Data_S_<sizeof(T)>*)vector + pos++);
} }
while(pos < items) while(pos < items)
*((Data_S_<sizeof(T)>*)vector + npos++) = *((Data_S_<sizeof(T)>*)vector + pos++); Move(vector + npos++, vector + pos++);
items = npos; items = npos;
} }
@ -347,15 +346,15 @@ void Vector<T>::RemoveIf(Condition c)
int i = 0; int i = 0;
for(; i < items; i++) // run to the first element without moving for(; i < items; i++) // run to the first element without moving
if(c(i)) { if(c(i)) {
(vector + i)->~T(); Destruct(vector + i);
break; break;
} }
int ti = i++; int ti = i++;
for(; i < items; i++) for(; i < items; i++)
if(c(i)) if(c(i))
(vector + i)->~T(); Destruct(vector + i);
else else
*((Data_S_<sizeof(T)>*)vector + ti++) = *((Data_S_<sizeof(T)>*)vector + i); Move(vector + ti++, vector + i);
items = ti; items = ti;
} }
@ -368,14 +367,14 @@ void Vector<T>::RawInsert(int q, int count)
if(items + count > alloc) { if(items + count > alloc) {
T *newvector = RawAlloc(alloc = max(alloc + count, int(alloc + ((unsigned)alloc >> 1)))); T *newvector = RawAlloc(alloc = max(alloc + count, int(alloc + ((unsigned)alloc >> 1))));
if(vector) { if(vector) {
memcpy_t(newvector, vector, q); Relocate(newvector, vector, q);
memcpy_t(newvector + q + count, vector + q, items - q); Relocate(newvector + q + count, vector + q, items - q);
RawFree(vector); RawFree(vector);
} }
vector = newvector; vector = newvector;
} }
else else
memmove((void *)(vector + q + count), (void *)(vector + q), (items - q) * sizeof(T)); InMove(vector + q + count, vector + q, items - q);
items += count; items += count;
} }
@ -442,7 +441,7 @@ void Vector<T>::Insert(int i, Vector<T>&& v) {
ASSERT(!vector || v.vector != vector); ASSERT(!vector || v.vector != vector);
if(v.items) { if(v.items) {
RawInsert(i, v.items); RawInsert(i, v.items);
memcpy_t(vector + i, v.vector, v.items); Relocate(vector + i, v.vector, v.items);
} }
RawFree(v.vector); RawFree(v.vector);
v.Zero(); v.Zero();
@ -455,7 +454,7 @@ void Vector<T>::InsertSplit(int i, Vector<T>& v, int from)
int n = v.GetCount() - from; int n = v.GetCount() - from;
if(n) { if(n) {
RawInsert(i, n); RawInsert(i, n);
memcpy_t(vector + i, v.vector + from, n); Relocate(vector + i, v.vector + from, n);
v.items = from; v.items = from;
} }
} }
@ -737,10 +736,10 @@ void BiVector<T>::ReAlloc(int newalloc) {
if(items) { if(items) {
int end = start + items; int end = start + items;
if(end <= alloc) if(end <= alloc)
memcpy_t(newvector, vector + start, end - start); Relocate(newvector, vector + start, end - start);
else { else {
memcpy_t(newvector, vector + start, alloc - start); Relocate(newvector, vector + start, alloc - start);
memcpy_t(newvector + alloc - start, vector, end - alloc); Relocate(newvector + alloc - start, vector, end - alloc);
} }
MemoryFree(vector); MemoryFree(vector);
} }

View file

@ -174,7 +174,7 @@ public:
XmlParser(Stream& in); XmlParser(Stream& in);
}; };
class XmlNode : Moveable< XmlNode, DeepCopyOption<XmlNode> > { class XmlNode : Moveable<XmlNode>, DeepCopyOption<XmlNode> {
int type; int type;
String text; String text;
Array<XmlNode> node; Array<XmlNode> node;

View file

@ -3,8 +3,6 @@ struct KeyInfo {
dword key[4]; dword key[4];
}; };
NTL_MOVEABLE(KeyInfo)
void RegisterKeyBinding(const char *group, const char *id, KeyInfo& (*info)()); void RegisterKeyBinding(const char *group, const char *id, KeyInfo& (*info)());
KeyInfo& AK_NULL(); KeyInfo& AK_NULL();

View file

@ -148,7 +148,7 @@ private:
byte cap; byte cap;
bool invert; bool invert;
}; };
struct Attr : Moveable<Attr, SimpleAttr> { struct Attr : Moveable<Attr>, SimpleAttr {
int mtx_serial; // used to detect changes to speedup preclip int mtx_serial; // used to detect changes to speedup preclip
WithDeepCopy<Vector<ColorStop>> color_stop; WithDeepCopy<Vector<ColorStop>> color_stop;

View file

@ -73,7 +73,7 @@ inline Size operator/(Size sz, Zoom m)
return Size(sz.cx / m, sz.cy / m); return Size(sz.cx / m, sz.cy / m);
} }
struct PageY : Moveable<PageY, RelOps<PageY> > { struct PageY : Moveable<PageY>, RelOps<PageY> {
int page; int page;
int y; int y;

View file

@ -90,7 +90,7 @@ struct Pdb : Debugger, ParentCtrl {
bool reference = false; // this is reference bool reference = false; // this is reference
}; };
struct Val : Moveable<Val, TypeInfo> { struct Val : Moveable<Val>, TypeInfo {
bool array = false; bool array = false;
bool rvalue = false; // data is loaded from debugee (if false, data pointed to by address) bool rvalue = false; // data is loaded from debugee (if false, data pointed to by address)
bool udt = false; // user defined type (e.g. struct..) bool udt = false; // user defined type (e.g. struct..)

View file

@ -5,7 +5,7 @@ struct FileLine : Moveable<FileLine> {
int line; int line;
}; };
struct LngEntry : Moveable<LngEntry, DeepCopyOption<LngEntry> > { struct LngEntry : Moveable<LngEntry>, DeepCopyOption<LngEntry> {
bool added; bool added;
VectorMap<int, String> text; VectorMap<int, String> text;
Vector<FileLine> fileline; Vector<FileLine> fileline;

View file

@ -41,8 +41,6 @@ static void png_user_warning_fn(png_structp png_ptr, png_const_charp warning_msg
LLOG("png warning: " << warning_msg); LLOG("png warning: " << warning_msg);
} }
NTL_MOVEABLE(png_color)
static Size GetDotSize(Size pixel_size, png_uint_32 x_ppm, png_uint_32 y_ppm, int unit_type) static Size GetDotSize(Size pixel_size, png_uint_32 x_ppm, png_uint_32 y_ppm, int unit_type)
{ {
if(unit_type != 1 || !x_ppm || !y_ppm) if(unit_type != 1 || !x_ppm || !y_ppm)