xref: /haiku/src/apps/icon-o-matic/import_export/styled_text/StyledTextImporter.cpp (revision 6936878097c7b85a6c497e1c3276bf75f646914b)
1 /*
2  * Copyright 2008-2009, 2023, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		François Revol <revol@free.fr>
7  *		Zardshard
8  */
9 
10 #include "StyledTextImporter.h"
11 
12 #include <new>
13 #include <stdint.h>
14 #include <stdio.h>
15 
16 #include <Alert.h>
17 #include <Archivable.h>
18 #include <ByteOrder.h>
19 #include <Catalog.h>
20 #include <DataIO.h>
21 #include <File.h>
22 #include <List.h>
23 #include <Locale.h>
24 #include <Message.h>
25 #include <NodeInfo.h>
26 #include <Shape.h>
27 #include <String.h>
28 #include <TextView.h>
29 
30 #include "Defines.h"
31 #include "Icon.h"
32 #include "PathSourceShape.h"
33 #include "Style.h"
34 #include "VectorPath.h"
35 
36 
37 #undef B_TRANSLATION_CONTEXT
38 #define B_TRANSLATION_CONTEXT "Icon-O-Matic-StyledTextImport"
39 //#define CALLED() printf("%s()\n", __FUNCTION__);
40 #define CALLED() do {} while (0)
41 
42 
43 using std::nothrow;
44 
45 class ShapeIterator : public BShapeIterator {
46  public:
47 	ShapeIterator(Icon *icon, PathSourceShape *to, BPoint offset, const char *name);
~ShapeIterator()48 	~ShapeIterator() {};
49 
50 	virtual	status_t	IterateMoveTo(BPoint *point);
51 	virtual	status_t	IterateLineTo(int32 lineCount, BPoint *linePts);
52 	virtual	status_t	IterateBezierTo(int32 bezierCount, BPoint *bezierPts);
53 	virtual	status_t	IterateClose();
54 
55  private:
56 	VectorPath				*CurrentPath();
57 	void					NextPath();
58 
59 	Icon *fIcon;
60 	PathSourceShape *fShape;
61 	VectorPath *fPath;
62 	BPoint fOffset;
63 	const char *fName;
64 	BPoint fLastPoint;
65 	bool fHasLastPoint;
66 };
67 
ShapeIterator(Icon * icon,PathSourceShape * to,BPoint offset,const char * name)68 ShapeIterator::ShapeIterator(Icon *icon, PathSourceShape *to, BPoint offset,
69 	const char *name)
70 {
71 	CALLED();
72 	fIcon = icon;
73 	fShape = to;
74 	fPath = NULL;
75 	fOffset = offset;
76 	fName = name;
77 	fLastPoint = offset;
78 	fHasLastPoint = false;
79 }
80 
81 status_t
IterateMoveTo(BPoint * point)82 ShapeIterator::IterateMoveTo(BPoint *point)
83 {
84 	CALLED();
85 	if (fPath)
86 		NextPath();
87 	if (!CurrentPath())
88 		return B_ERROR;
89 	//fPath->AddPoint(fOffset + *point);
90 	fLastPoint = fOffset + *point;
91 	fHasLastPoint = true;
92 	return B_OK;
93 }
94 
95 status_t
IterateLineTo(int32 lineCount,BPoint * linePts)96 ShapeIterator::IterateLineTo(int32 lineCount, BPoint *linePts)
97 {
98 	CALLED();
99 	if (!CurrentPath())
100 		return B_ERROR;
101 	while (lineCount--) {
102 		fPath->AddPoint(fOffset + *linePts);
103 		fLastPoint = fOffset + *linePts;
104 		fHasLastPoint = true;
105 		linePts++;
106 	}
107 	return B_OK;
108 }
109 
110 status_t
IterateBezierTo(int32 bezierCount,BPoint * bezierPts)111 ShapeIterator::IterateBezierTo(int32 bezierCount, BPoint *bezierPts)
112 {
113 	CALLED();
114 	if (!CurrentPath())
115 		return B_ERROR;
116 
117 	BPoint firstPoint(fLastPoint);
118 	fLastPoint = fOffset + bezierPts[bezierCount * 3 - 1];
119 
120 	// first point
121 	if (firstPoint == fLastPoint) {
122 		// combine the first and the last point
123 		fPath->AddPoint(firstPoint,
124 			fOffset + bezierPts[bezierCount * 3 - 2], fOffset + bezierPts[0], false);
125 			// Mark the points as disconnected for now. CleanUp will change this if necessary.
126 		fPath->SetClosed(true);
127 	} else {
128 		fPath->AddPoint(firstPoint, firstPoint, fOffset + bezierPts[0], false);
129 	}
130 
131 	// middle points
132 	for (int i = 1; i + 2 < bezierCount * 3; i += 3) {
133 		fPath->AddPoint(fOffset + bezierPts[i + 1],
134 			fOffset + bezierPts[i + 0], fOffset + bezierPts[i + 2], false);
135 	}
136 
137 	// last point
138 	if (firstPoint != fLastPoint) {
139 		fPath->AddPoint(fLastPoint,
140 			fOffset + bezierPts[bezierCount * 3 - 2], fLastPoint, false);
141 	}
142 
143 	fHasLastPoint = true;
144 	return B_OK;
145 }
146 
147 status_t
IterateClose()148 ShapeIterator::IterateClose()
149 {
150 	CALLED();
151 	if (!CurrentPath())
152 		return B_ERROR;
153 	fPath->SetClosed(true);
154 	NextPath();
155 	fHasLastPoint = false;
156 	return B_OK;
157 }
158 
159 VectorPath *
CurrentPath()160 ShapeIterator::CurrentPath()
161 {
162 	CALLED();
163 	if (fPath)
164 		return fPath;
165 	fPath = new (nothrow) VectorPath();
166 	fPath->SetName(fName);
167 	return fPath;
168 }
169 
170 void
NextPath()171 ShapeIterator::NextPath()
172 {
173 	CALLED();
174 	if (fPath) {
175 		fPath->CleanUp();
176 		fIcon->Paths()->AddItem(fPath);
177 		fShape->Paths()->AddItem(fPath);
178 	}
179 	fPath = NULL;
180 }
181 
182 
183 // #pragma mark -
184 
185 // constructor
StyledTextImporter()186 StyledTextImporter::StyledTextImporter()
187 	: Importer(),
188 	  fStyleMap(NULL),
189 	  fStyleCount(0)
190 {
191 	CALLED();
192 }
193 
194 // destructor
~StyledTextImporter()195 StyledTextImporter::~StyledTextImporter()
196 {
197 	CALLED();
198 }
199 
200 // Import
201 status_t
Import(Icon * icon,BMessage * clipping)202 StyledTextImporter::Import(Icon* icon, BMessage* clipping)
203 {
204 	CALLED();
205 	const char *text;
206 	ssize_t textLength;
207 
208 	if (clipping == NULL)
209 		return ENOENT;
210 	if (clipping->FindData("text/plain",
211 		B_MIME_TYPE, (const void **)&text, &textLength) == B_OK) {
212 		text_run_array *runs = NULL;
213 		ssize_t runsLength;
214 		if (clipping->FindData("application/x-vnd.Be-text_run_array",
215 			B_MIME_TYPE, (const void **)&runs, &runsLength) < B_OK)
216 			runs = NULL;
217 		BString str(text, textLength);
218 		return _Import(icon, str.String(), runs);
219 	}
220 	return ENOENT;
221 }
222 
223 // Import
224 status_t
Import(Icon * icon,const entry_ref * ref)225 StyledTextImporter::Import(Icon* icon, const entry_ref* ref)
226 {
227 	CALLED();
228 	status_t err;
229 	BFile file(ref, B_READ_ONLY);
230 	err = file.InitCheck();
231 	if (err < B_OK)
232 		return err;
233 
234 	BNodeInfo info(&file);
235 	char mime[B_MIME_TYPE_LENGTH];
236 	err = info.GetType(mime);
237 	if (err < B_OK)
238 		return err;
239 
240 	if (strncmp(mime, "text/plain", B_MIME_TYPE_LENGTH))
241 		return EINVAL;
242 
243 	off_t size;
244 	err = file.GetSize(&size);
245 	if (err < B_OK)
246 		return err;
247 	if (size > 1 * 1024 * 1024) // Don't load files that big
248 		return E2BIG;
249 
250 	BMallocIO mio;
251 	mio.SetSize((size_t)size + 1);
252 	memset((void *)mio.Buffer(), 0, (size_t)size + 1);
253 
254 	// TODO: read runs from attribute
255 
256 	return _Import(icon, (const char *)mio.Buffer(), NULL);
257 }
258 
259 
260 // _Import
261 status_t
_Import(Icon * icon,const char * text,text_run_array * runs)262 StyledTextImporter::_Import(Icon* icon, const char *text, text_run_array *runs)
263 {
264 	CALLED();
265 	status_t ret = Init(icon);
266 	if (ret < B_OK) {
267 		printf("StyledTextImporter::Import() - Init() error: %s\n",
268 			strerror(ret));
269 		return ret;
270 	}
271 
272 	BString str(text);
273 	if (str.Length() > 50) {
274 		BAlert* alert = new BAlert(B_TRANSLATE("Text too long"),
275 			B_TRANSLATE("The text you are trying to import is quite long, "
276 				"are you sure?"),
277 			B_TRANSLATE("Import text"), B_TRANSLATE("Cancel"), NULL,
278 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
279 		if (alert->Go())
280 			return B_CANCELED;
281 	}
282 
283 	// import run colors as styles
284 	if (runs) {
285 		delete[] fStyleMap;
286 		fStyleMap = new struct style_map[runs->count];
287 		for (int32 i = 0; runs && i < runs->count; i++) {
288 			_AddStyle(icon, &runs->runs[i]);
289 		}
290 	}
291 
292 	int32 currentRun = 0;
293 	text_run *run = NULL;
294 	if (runs)
295 		run = &runs->runs[0];
296 	int32 len = str.Length();
297 	int32 chars = str.CountChars();
298 	BPoint origin(0, 0);
299 	BPoint offset(origin);
300 
301 	for (int32 i = 0, c = 0; i < len && c < chars; c++) {
302 		// make sure we are still on the (good) run
303 		while (run && currentRun < runs->count - 1 &&
304 			i >= runs->runs[currentRun + 1].offset) {
305 			run = &runs->runs[++currentRun];
306 			//printf("switching to run %d\n", currentRun);
307 		}
308 
309 		int charLen;
310 		for (charLen = 1; str.ByteAt(i + charLen) & 0x80; charLen++);
311 
312 		BShape glyph;
313 		BShape *glyphs[1] = { &glyph };
314 		BFont font(be_plain_font);
315 		if (run)
316 			font = run->font;
317 
318 		// first char
319 		if (offset == BPoint(0,0)) {
320 			font_height height;
321 			font.GetHeight(&height);
322 			origin.y += height.ascent;
323 			offset = origin;
324 		}
325 		// LF
326 		if (str[i] == '\n') {
327 			// XXX: should take the MAX() for the line
328 			// XXX: should use descent + leading from previous line
329 			font_height height;
330 			font.GetHeight(&height);
331 			origin.y += height.ascent + height.descent + height.leading;
332 			offset = origin;
333 			i++;
334 			continue;
335 		}
336 
337 		float charWidth;
338 		charWidth = font.StringWidth(str.String() + i, charLen);
339 		//printf("StringWidth( %d) = %f\n", charLen, charWidth);
340 		BString glyphName(str.String() + i, charLen);
341 		glyphName.Prepend("Glyph (");
342 		glyphName.Append(")");
343 
344 		font.GetGlyphShapes((str.String() + i), 1, glyphs);
345 		if (glyph.Bounds().IsValid()) {
346 			//offset.x += glyph.Bounds().Width();
347 			offset.x += charWidth;
348 			PathSourceShape* shape = new (nothrow) PathSourceShape(NULL);
349 			if (shape == NULL)
350 				return B_NO_MEMORY;
351 			shape->SetName(glyphName.String());
352 			if (!icon->Shapes()->AddItem(shape)) {
353 				delete shape;
354 				return B_NO_MEMORY;
355 			}
356 			for (int j = 0; run && j < fStyleCount; j++) {
357 				if (fStyleMap[j].run == run) {
358 					shape->SetStyle(fStyleMap[j].style);
359 					break;
360 				}
361 			}
362 			ShapeIterator iterator(icon, shape, offset, glyphName.String());
363 			if (iterator.Iterate(&glyph) < B_OK)
364 				return B_ERROR;
365 
366 		}
367 
368 		// skip the rest of UTF-8 char bytes
369 		for (i++; i < len && str[i] & 0x80; i++);
370 	}
371 
372 	delete[] fStyleMap;
373 	fStyleMap = NULL;
374 
375 	return B_OK;
376 }
377 
378 // #pragma mark -
379 
380 // _AddStyle
381 status_t
_AddStyle(Icon * icon,text_run * run)382 StyledTextImporter::_AddStyle(Icon *icon, text_run *run)
383 {
384 	CALLED();
385 	if (!run)
386 		return EINVAL;
387 	rgb_color color = run->color;
388 	Style* style = new(std::nothrow) Style(color);
389 	if (style == NULL)
390 		return B_NO_MEMORY;
391 	char name[30];
392 	sprintf(name, B_TRANSLATE_COMMENT("Color (#%02x%02x%02x)",
393 		"Style name after dropping a color"),
394 		color.red, color.green, color.blue);
395 	style->SetName(name);
396 
397 	bool found = false;
398 	for (int i = 0; i < fStyleCount; i++) {
399 		if (*style == *(fStyleMap[i].style)) {
400 			delete style;
401 			style = fStyleMap[i].style;
402 			found = true;
403 			break;
404 		}
405 	}
406 
407 	if (!found && !icon->Styles()->AddItem(style)) {
408 		delete style;
409 		return B_NO_MEMORY;
410 	}
411 
412 	fStyleMap[fStyleCount].run = run;
413 	fStyleMap[fStyleCount].style = style;
414 	fStyleCount++;
415 
416 	return B_OK;
417 }
418 
419 // _AddPaths
420 status_t
_AddPaths(Icon * icon,BShape * shape)421 StyledTextImporter::_AddPaths(Icon *icon, BShape *shape)
422 {
423 	CALLED();
424 	return B_ERROR;
425 }
426 
427 // _AddShape
428 status_t
_AddShape(Icon * icon,BShape * shape,text_run * run)429 StyledTextImporter::_AddShape(Icon *icon, BShape *shape, text_run *run)
430 {
431 	CALLED();
432 	return B_ERROR;
433 }
434