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