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